
#include <QDebug>
#include <QHash>

#include "dpkg-db.h"

/* From DPKG Sources Code */
// Version Compare
static bool c_isdigit(int c)
{
    return QChar(c).isDigit();
}
static bool c_isalpha(int c)
{
    return QChar(c).isNumber();
}
static int order(int c)
{
    if (c_isdigit(c)) { return 0; }
    if (c_isalpha(c)) { return c; }
    if (c == '~') { return -1; }
    if (c) { return c + 256; }
    return 0;
}
static int verrevcmp(const char *a, const char *b)
{
    if (a == NULL) { a = ""; }
    if (b == NULL) { b = ""; }

    while (*a || *b) {
        int first_diff = 0;

        while ((*a && !c_isdigit(*a)) || (*b && !c_isdigit(*b))) {
            int ac = order(*a);
            int bc = order(*b);

            if (ac != bc) {
                return ac - bc;
            }

            a++;
            b++;
        }
        while (*a == '0') {            a++; }
        while (*b == '0') {            b++; }
        while (c_isdigit(*a) && c_isdigit(*b)) {
            if (!first_diff) {
                first_diff = *a - *b;
            }
            a++;
            b++;
        }

        if (c_isdigit(*a)) { return 1; }
        if (c_isdigit(*b)) { return -1; }
        if (first_diff) { return first_diff; }
    }

    return 0;
}
static int compare(const PKGVersion &a, const PKGVersion &b) {
    if (a.epoch > b.epoch) { return 1; }
    if (a.epoch < b.epoch) { return -1; }
    // int rc = verrevcmp(a.version.toLocal8Bit(), b.version.toLocal8Bit());
    // if (rc) {
    //     return rc;
    // }
    return verrevcmp(a.version.toLocal8Bit(), b.version.toLocal8Bit());
}
/* From DPKG Sources Code */

/* PKGVersion */
PKGVersion::PKGVersion(int epoch, const QString &version, const QString &revision)
{
    this->epoch = epoch;
    this->version = version;
    this->revision = revision;
}
PKGVersion::PKGVersion(const QString &verstr)
{
    fromString(verstr);
}
PKGVersion::~PKGVersion()
{
    clear();
}
void PKGVersion::clear()
{
    epoch = 0;
    version = QString();
    revision = QString();
}
bool PKGVersion::operator>(const PKGVersion &dist)
{
    return (compare(*this, dist) > 0);
}
bool PKGVersion::operator<(const PKGVersion &dist)
{
    return (compare(*this, dist) < 0);
}
bool PKGVersion::operator==(const PKGVersion &dist)
{
    return (compare(*this, dist) == 0);
}
QString PKGVersion::toString() const
{
    QString verstr = QString(QLatin1String("%1-%2")).arg(version, revision);
    if (epoch != 0) {
        verstr = QString(QLatin1String("%1:%2")).arg(QString::number(epoch), verstr);
    }
    return verstr;
}
const PKGVersion &PKGVersion::fromString(const QString &verstr)
{
    if (!verstr.isEmpty()) {
        const QLatin1String colon(":");
        QString ver = verstr.trimmed();
        if (ver.contains(colon)) {
            QStringList items = ver.split(colon);
            this->epoch = items.at(0).toUInt();
            ver = items.at(1);
        }
        int hyphen_index = ver.lastIndexOf(QLatin1String("-"));
        this->version = ver.left(hyphen_index);
        this->revision = ver.right(ver.count() - hyphen_index - 1);
    }
    return *this;
}

/* PKGDepend */
namespace PKGDependGlobal {
static bool inited = false;
QHash<QString, PKGDepend::Type> G_HASH_DEPTYPE;
QHash<int, QStringList> G_HASH_VERCHECK;
static void Init()
{
    if (inited) { return; }

    /* PKGDepend::Type */
    G_HASH_DEPTYPE.insert(QLatin1String("Replaces"),       PKGDepend::Type::dep_replaces);
    G_HASH_DEPTYPE.insert(QLatin1String("Provides"),       PKGDepend::Type::dep_provides);
    G_HASH_DEPTYPE.insert(QLatin1String("Depends"),        PKGDepend::Type::dep_depends);
    G_HASH_DEPTYPE.insert(QLatin1String("Pre-Depends"),    PKGDepend::Type::dep_predepends);
    G_HASH_DEPTYPE.insert(QLatin1String("Recommends"),     PKGDepend::Type::dep_recommends);
    G_HASH_DEPTYPE.insert(QLatin1String("Suggests"),       PKGDepend::Type::dep_suggests);
    G_HASH_DEPTYPE.insert(QLatin1String("Breaks"),         PKGDepend::Type::dep_breaks);
    G_HASH_DEPTYPE.insert(QLatin1String("Conflicts"),      PKGDepend::Type::dep_conflicts);
    G_HASH_DEPTYPE.insert(QLatin1String("Enhances"),       PKGDepend::Type::dep_enhances);
    G_HASH_DEPTYPE.insert(QLatin1String("Recommended"),    PKGDepend::Type::dep_recommended);
    G_HASH_DEPTYPE.insert(QLatin1String("Optional"),       PKGDepend::Type::dep_optional);
    /* PKGDepend::VerCheck */
    G_HASH_VERCHECK.insert(static_cast<int>(PKGDepend::VerCheck::dep_nocheck), QStringList());
    G_HASH_VERCHECK.insert(static_cast<int>(PKGDepend::VerCheck::dep_equal), QStringList({ QLatin1String("="), QLatin1String("==") }));
    G_HASH_VERCHECK.insert(static_cast<int>(PKGDepend::VerCheck::dep_great), QStringList({ QLatin1String(">"), QLatin1String(">>") }));
    G_HASH_VERCHECK.insert(static_cast<int>(PKGDepend::VerCheck::dep_less), QStringList({ QLatin1String("<"), QLatin1String("<<") }));
    G_HASH_VERCHECK.insert(static_cast<int>(PKGDepend::VerCheck::dep_great_or_equal), QStringList({ QLatin1String(">=") }));
    G_HASH_VERCHECK.insert(static_cast<int>(PKGDepend::VerCheck::dep_less_or_equal), QStringList({ QLatin1String("<=") }));

    inited = true;
}
}
PKGDepend::PKGDepend(QSharedPointer<PKGInfo> package, PKGDepend::Type type)
{
    if (package.isNull()) {
        this->package = QSharedPointer<PKGInfo>(new PKGInfo);
    } else {
        this->package = package;
    }
    this->type = type;
    this->vercheck = PKGDepend::VerCheck::dep_nocheck;
}
PKGDepend::PKGDepend(const QString &str, const QString &type)
{
    if (this->package.isNull()) {
        this->package = QSharedPointer<PKGInfo>(new PKGInfo);
    }
    fromString(str, type);
}
PKGDepend::~PKGDepend()
{
    clear();
}
void PKGDepend::clear()
{
    alternatives.clear();
    package.reset();
    type = PKGDepend::Type::dep_invalid;
    vercheck = PKGDepend::VerCheck::dep_nocheck;
}
QString PKGDepend::toString() const
{
    QString depend;
    if (this->vercheck == PKGDepend::VerCheck::dep_nocheck) {
        depend = this->package->name;
    } else {
        depend = QString(QLatin1String("%1 (%2 %3)")).arg(this->package->name, ConvVerCheck(this->vercheck), this->package->version.toString());
    }
    if (this->alternatives.count() > 0) {
        QStringList alts;
        alts.append(depend);
        for (const QSharedPointer<PKGDepend> &alt : qAsConst(this->alternatives)) {
            alts.append(alt->toString());
        }
        depend = alts.join(QLatin1String(" | "));
    }
    return depend;
}
PKGDepend &PKGDepend::fromString(const QString &str, const QString &type)
{
    if (str.contains(QLatin1String("("))) {
        if (!str.contains(QLatin1String(")"))) {
            qCritical().noquote() << "Invalid depend string";
            return *this;
        } else {
            QStringList items = str.split(QLatin1String(" "));
            if (items.count() != 3) {
                return *this;
            }
            const QString &name = items.at(0);
            const QString &op = items[1].remove(0, 1);
            const QString &ver = items[2].remove(items.at(2).count() - 1, 1);
            this->package->name = name;
            this->package->version.fromString(ver);
            this->vercheck = ConvVerCheck(op);
            this->type = ConvType(type);
        }
    } else {
        this->package->name = str;
        this->vercheck = PKGDepend::VerCheck::dep_nocheck;
        this->type = ConvType(type);
    }
    return *this;
}
QString PKGDepend::ConvType(PKGDepend::Type value) const
{
    PKGDependGlobal::Init();
    return PKGDependGlobal::G_HASH_DEPTYPE.key(value, QString());
}
PKGDepend::Type PKGDepend::ConvType(const QString &value) const
{
    PKGDependGlobal::Init();
    return PKGDependGlobal::G_HASH_DEPTYPE.value(value, PKGDepend::Type::dep_invalid);
}
QString PKGDepend::ConvVerCheck(PKGDepend::VerCheck value) const
{
    PKGDependGlobal::Init();
    QStringList vals = PKGDependGlobal::G_HASH_VERCHECK.value(static_cast<int>(value), QStringList());
    if (vals.isEmpty()) {
        return QString();
    } else {
        return vals.at(0);
    }
}
PKGDepend::VerCheck PKGDepend::ConvVerCheck(const QString &value) const
{
    PKGDependGlobal::Init();
    QList<QStringList> vals = PKGDependGlobal::G_HASH_VERCHECK.values();
    for (int i=0; i< vals.count(); i++) {
        if (vals.at(i).contains(value)) {
            QList<int> keys = PKGDependGlobal::G_HASH_VERCHECK.keys();
            return static_cast<PKGDepend::VerCheck>(keys.at(i));
        }
    }
    return PKGDepend::VerCheck::dep_nocheck;
}

/* PKGConfFile */
PKGConfFile::PKGConfFile(const QString &name, const QString &hash, bool obsolete, bool remove_on_upgrade)
{
    this->name = name;
    this->hash = hash;
    this->obsolete = obsolete;
    this->remove_on_upgrade = remove_on_upgrade;
}

/* PKGArchiveDetails */
PKGArchiveDetails::PKGArchiveDetails(const QString &name, const QString &msdosname, const QString &size, const QString &md5sum)
{
    this->name = name;
    this->msdosname = msdosname;
    this->size = size;
    this->md5sum = md5sum;
}
void PKGArchiveDetails::field(const QString &member, const QString &value)
{
    if (member == QLatin1String("Filename")) { this->name = value; }
    if (member == QLatin1String("MSDOS-Filename")) { this->msdosname = value; }
    if (member == QLatin1String("Size")) { this->size = value; }
    if (member == QLatin1String("MD5sum")) { this->md5sum = value; }
}

/* PKGArch */
QStringList G_SUPPORT_ARCHS = {
    QLatin1String("amd64"),
    QLatin1String("arm64"),
    QLatin1String("armhf"),
    QLatin1String("i386"),
    QLatin1String("loongarch64"),
    QLatin1String("mips64el"),
    QLatin1String("riscv64"),
    QLatin1String("ppc64el"),
    QLatin1String("s390x"),
};
PKGArch::PKGArch()
{
    this->type = PKGArch::Type::DPKG_ARCH_EMPTY;
}
PKGArch::PKGArch(QString name, PKGArch::Type type)
{
    this->name = name;
    this->type = type;
}
PKGArch::PKGArch(const QString &archstr)
{
    if (archstr.isEmpty()) {
        this->type = PKGArch::Type::DPKG_ARCH_EMPTY;
        return;
    }
    this->name = archstr;
    if (archstr == QLatin1String("all")) {
        this->type = PKGArch::Type::DPKG_ARCH_ALL;
    }
    if (archstr == QLatin1String("any")) {
        this->type = PKGArch::Type::DPKG_ARCH_WILDCARD;
    }
    if (G_SUPPORT_ARCHS.contains(archstr)) {
        this->type = PKGArch::Type::DPKG_ARCH_NATIVE;
    } else {
        if (!archstr.at(0).isLetter()) {
            this->type = PKGArch::Type::DPKG_ARCH_ILLEGAL;
        } else {
            this->type = PKGArch::Type::DPKG_ARCH_UNKNOWN;
        }
    }
}

/* PKGInfo */
namespace PKGInfoGlobal {
static bool inited = false;
QHash<QString, PKGInfo::Want> G_HASH_WANT;
QHash<QString, PKGInfo::EFlag> G_HASH_EFLAG;
QHash<QString, PKGInfo::Status> G_HASH_STATUS;
QHash<QString, PKGInfo::Priority> G_HASH_PRIORITY;
QHash<QString, PKGInfo::MultiArch> G_HASH_MULTIARCH;
static void Init()
{
    if (inited) { return; }

    /* PKGInfo::Want */
    G_HASH_WANT.insert(QLatin1String("unknown"),            PKGInfo::Want::PKG_WANT_UNKNOWN);
    G_HASH_WANT.insert(QLatin1String("install"),            PKGInfo::Want::PKG_WANT_INSTALL);
    G_HASH_WANT.insert(QLatin1String("hold"),               PKGInfo::Want::PKG_WANT_HOLD);
    G_HASH_WANT.insert(QLatin1String("deinstall"),          PKGInfo::Want::PKG_WANT_DEINSTALL);
    G_HASH_WANT.insert(QLatin1String("purge"),              PKGInfo::Want::PKG_WANT_PURGE);
    G_HASH_WANT.insert(QLatin1String("sentinel"),           PKGInfo::Want::PKG_WANT_SENTINEL);
    /* PKGInfo::EFlag */
    G_HASH_EFLAG.insert(QLatin1String("ok"),                PKGInfo::EFlag::PKG_EFLAG_OK);
    G_HASH_EFLAG.insert(QLatin1String("reinstreq"),         PKGInfo::EFlag::PKG_EFLAG_REINSTREQ);
    /* PKGInfo::Status */
    G_HASH_STATUS.insert(QLatin1String("not-installed"),    PKGInfo::Status::PKG_STAT_NOTINSTALLED);
    G_HASH_STATUS.insert(QLatin1String("config-files"),     PKGInfo::Status::PKG_STAT_CONFIGFILES);
    G_HASH_STATUS.insert(QLatin1String("half-installed"),   PKGInfo::Status::PKG_STAT_HALFINSTALLED);
    G_HASH_STATUS.insert(QLatin1String("unpacked"),         PKGInfo::Status::PKG_STAT_UNPACKED);
    G_HASH_STATUS.insert(QLatin1String("half-configured"),  PKGInfo::Status::PKG_STAT_HALFCONFIGURED);
    G_HASH_STATUS.insert(QLatin1String("triggers-awaited"), PKGInfo::Status::PKG_STAT_TRIGGERSAWAITED);
    G_HASH_STATUS.insert(QLatin1String("triggers-pending"), PKGInfo::Status::PKG_STAT_TRIGGERSPENDING);
    G_HASH_STATUS.insert(QLatin1String("installed"),        PKGInfo::Status::PKG_STAT_INSTALLED);
    /* PKGInfo::Priority */
    G_HASH_PRIORITY.insert(QLatin1String("required"),       PKGInfo::Priority::PKG_PRIO_REQUIRED);
    G_HASH_PRIORITY.insert(QLatin1String("important"),      PKGInfo::Priority::PKG_PRIO_IMPORTANT);
    G_HASH_PRIORITY.insert(QLatin1String("standard"),       PKGInfo::Priority::PKG_PRIO_STANDARD);
    G_HASH_PRIORITY.insert(QLatin1String("optional"),       PKGInfo::Priority::PKG_PRIO_OPTIONAL);
    G_HASH_PRIORITY.insert(QLatin1String("extra"),          PKGInfo::Priority::PKG_PRIO_EXTRA);
    G_HASH_PRIORITY.insert(QLatin1String("other"),          PKGInfo::Priority::PKG_PRIO_OTHER);
    G_HASH_PRIORITY.insert(QLatin1String("unknown"),        PKGInfo::Priority::PKG_PRIO_UNKNOWN);
    G_HASH_PRIORITY.insert(QLatin1String("unset"),          PKGInfo::Priority::PKG_PRIO_UNSET);
    /* PKGInfo::MultiArch */
    G_HASH_MULTIARCH.insert(QLatin1String("no"),            PKGInfo::MultiArch::PKG_MULTIARCH_NO);
    G_HASH_MULTIARCH.insert(QLatin1String("same"),          PKGInfo::MultiArch::PKG_MULTIARCH_SAME);
    G_HASH_MULTIARCH.insert(QLatin1String("allowed"),       PKGInfo::MultiArch::PKG_MULTIARCH_ALLOWED);
    G_HASH_MULTIARCH.insert(QLatin1String("foreign"),       PKGInfo::MultiArch::PKG_MULTIARCH_FOREIGN);

    inited = true;
}
}
bool PKGInfo::isValid()
{
    if (this->name.isEmpty()) { return false; }
    return true;
}
QString PKGInfo::depends() const
{
    QStringList deps;
    for (const PKGDepend &dep : qAsConst(this->dep_depends)) {
        deps.append(dep.toString());
    }
    return deps.join(QLatin1String(", "));
}

QString PKGInfo::ConvWant(PKGInfo::Want value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_WANT.key(value, QString());
}
PKGInfo::Want PKGInfo::ConvWant(const QString &value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_WANT.value(value, PKGInfo::Want::PKG_WANT_UNKNOWN);
}

QString PKGInfo::ConvEflag(PKGInfo::EFlag value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_EFLAG.key(value, QString());
}
PKGInfo::EFlag PKGInfo::ConvEflag(const QString &value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_EFLAG.value(value, PKGInfo::EFlag::PKG_EFLAG_OK);
}

QString PKGInfo::ConvStatus(PKGInfo::Status value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_STATUS.key(value, QString());
}
PKGInfo::Status PKGInfo::ConvStatus(const QString &value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_STATUS.value(value, PKGInfo::Status::PKG_STAT_NOTINSTALLED);
}

QString PKGInfo::ConvPriority(PKGInfo::Priority value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_PRIORITY.key(value, QString());
}
PKGInfo::Priority PKGInfo::ConvPriority(const QString &value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_PRIORITY.value(value, PKGInfo::Priority::PKG_PRIO_UNSET);
}

QString PKGInfo::ConvMultiArch(PKGInfo::MultiArch value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_MULTIARCH.key(value, QString());
}
PKGInfo::MultiArch PKGInfo::ConvMultiArch(const QString &value) const
{
    PKGInfoGlobal::Init();
    return PKGInfoGlobal::G_HASH_MULTIARCH.value(value, PKGInfo::MultiArch::PKG_MULTIARCH_NO);
}
